package org.aner.tester.generator.data;

import org.aner.tester.constant.Constant;
import org.aner.tester.exception.NotMatchedException;
import org.aner.tester.generator.TypeManager;
import org.aner.tester.object.ValueObject;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.lang.reflect.Array;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 类型随机值生成器
 *
 * @author aner
 * @version 1.0
 * @date 2020/12/11 14:46
 */
public class ValueGenerator {

    protected static Logger logger = LoggerFactory.getLogger(ValueGenerator.class);

    /**
     * Random data generator
     */
    private static final Random randomNumberGenerator = new Random();

    /**
     * the data generator collection, containing:java basic type、list（without generic type）and map（without generic type）
     */
    public static Map<String , Supplier> basicDataGeneratorMap = new HashMap<>();

    /**
     * data generator collection, containing:list and map (with generic type)
     */
    public static Map<String , Function<ValueObject, Object>> genericDataGeneratorMap = new HashMap<>();

    /**
     * random data
     */
    private static String randomData = "1234567890abcdefghijklmnopqrstuvwxyz";;

    static {
        basicDataGeneratorMap.put(Constant.STRING, ValueGenerator::stringRandom);
        basicDataGeneratorMap.put(Constant.INTEGER, ValueGenerator::intRandom);
        basicDataGeneratorMap.put(Constant.INT, ValueGenerator::intRandom);
        basicDataGeneratorMap.put(Constant.DOUBLE, ValueGenerator::doubleRandom);
        basicDataGeneratorMap.put(Constant.DOUBLE_OBJECT, ValueGenerator::doubleRandom);
        basicDataGeneratorMap.put(Constant.LONG_OBJECT, ValueGenerator::longRandom);
        basicDataGeneratorMap.put(Constant.LONG, ValueGenerator::longRandom);
        basicDataGeneratorMap.put(Constant.FLOAT_OBJECT, ValueGenerator::floatRandom);
        basicDataGeneratorMap.put(Constant.FLOAT, ValueGenerator::floatRandom);
        basicDataGeneratorMap.put(Constant.DATE, ValueGenerator::dateRandom);
        basicDataGeneratorMap.put(Constant.BOOLEAN_OBJECT, ValueGenerator::booleanRandom);
        basicDataGeneratorMap.put(Constant.BOOLEAN, ValueGenerator::booleanRandom);
        basicDataGeneratorMap.put(Constant.BYTE_OBJECT, ValueGenerator::byteRandom);
        basicDataGeneratorMap.put(Constant.BYTE, ValueGenerator::byteRandom);
        basicDataGeneratorMap.put(Constant.LIST, ValueGenerator::listRandom);
        basicDataGeneratorMap.put(Constant.MAP, ValueGenerator::mapRandom);
        basicDataGeneratorMap.put(Constant.OBJECT, ValueGenerator::intStringRandom);

        genericDataGeneratorMap.put(Constant.ARRAY, (ValueObject fieldObject) -> arrayRandom(fieldObject));
        genericDataGeneratorMap.put(Constant.GENERIC_LIST, (ValueObject fieldObject) -> listRandom(fieldObject));
        genericDataGeneratorMap.put(Constant.GENERIC_MAP, (ValueObject fieldObject) -> mapRandom(fieldObject));


    }

    public static Object generate(ValueObject valueObject) {
        try {
            if (valueObject.isGenericCollection()) {
                return getGenericCollectionValue(valueObject);
            } else {
                return getConcreteTypeValue(valueObject);
            }
        } catch (NotMatchedException e) {
            logger.error(e.getMessage());
            return null;
        }
    }

    /**
     * get data of java basic type、list（without generic type）and map（without generic type）
     *
     * @param valueObject
     * @return
     */
    public static Object getConcreteTypeValue(ValueObject valueObject) {
        String simpleTypeName = valueObject.getSimpleTypeName();
        // java basic、list(without generic type) and Map(without generic type)
        if (basicDataGeneratorMap.containsKey(simpleTypeName)) {
            return basicDataGeneratorMap.get(simpleTypeName).get();
            // custom type
        } else if (TypeManager.customContains(valueObject.getTypeName())){
            return ValueGenerator.generateCustom(valueObject.getTypeName(), valueObject.getLevelCount() + 1);
        } else {
            throw NotMatchedException.error(simpleTypeName);
        }
    }

    /**
     * get data of array、list(with generic type) and map (with generic type)
     *
     * @param fieldObject
     * @return
     */
    public static Object getGenericCollectionValue(ValueObject fieldObject) {
        return genericDataGeneratorMap.get(fieldObject.getSimpleTypeName()).apply(fieldObject);
    }

    /**
     * get data of custom object
     *
     * @param typeName
     * @return
     */
    public static Object generateCustom(String typeName) {
        try {
            return new ObjectGenerator().generate(TypeManager.getCustomType(typeName));
        } catch (NotMatchedException e) {
            logger.error(e.getMessage());
            return newEmptyObject();
        }

    }

    public static Object generateCustom(String typeName, int nestedLevelCount) {
        try {
            return new ObjectGenerator().generate(
                    TypeManager.getCustomType(typeName),nestedLevelCount);
        } catch (NotMatchedException e) {
            logger.error(e.getMessage());
            return newEmptyObject();
        }

    }

    public static Object newEmptyObject() {
        return new Object();
    }

    private static String stringRandom() {
        int length = randomNumberGenerator.nextInt(5) + 2;
        char[] chr = randomData.toCharArray();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < length; i++) {
            buffer.append(chr[randomNumberGenerator.nextInt(chr.length)]);
        }
        return buffer.toString();
    }

    private static String intStringRandom() {
        String str = "0123456789";
        int length = randomNumberGenerator.nextInt(5) + 2;
        char[] chr = str.toCharArray();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < length; i++) {
            buffer.append(chr[randomNumberGenerator.nextInt(chr.length)]);
        }
        return buffer.toString();
    }


    public static int intRandom() {
        return randomNumberGenerator.nextInt(2);
    }

    public static long longRandom() {
        return randomNumberGenerator.nextInt(4);
    }

    public static byte byteRandom() {
        return (byte)randomNumberGenerator.nextInt(2);
    }

    public static double doubleRandom() {
        return randomNumberGenerator.nextDouble();
    }

    public static float floatRandom() {
        return randomNumberGenerator.nextFloat();
    }
    public static boolean booleanRandom() {
        return randomNumberGenerator.nextDouble() > 0.5;
    }

    public static Date dateRandom() {
        return new Date();
    }

    public static List listRandom() {
        String str = "0123456789";
        int length = randomNumberGenerator.nextInt(5) + 2;
        List dataList = new ArrayList();
        for (int i = 0; i < length; i++) {
            dataList.add(str.substring(randomNumberGenerator.nextInt(10)));
        }
        return dataList;
    }

    /**
     *data generator of list and map (with generic type)
     *
     * @param valueObject
     * @return
     */
    public static List listRandom(ValueObject valueObject) {
        if (valueObject.getLevelCount() > ObjectGenerator.getNestedLevelCount()) {
            return null;
        }
        int length = randomNumberGenerator.nextInt(5) + 2;
        List dataList = new ArrayList();
        String genericTypeSimpleName = valueObject.getGenericTypeSimpleNameList().get(0);
        String genericTypeName = valueObject.getGenericTypeNameList().get(0);
        // basic type
        if (TypeManager.basicContains(genericTypeSimpleName)) {
            for (int i = 0; i < length; i++) {
                dataList.add(basicDataGeneratorMap.get(genericTypeSimpleName).get());
            }
            // custom type
        } else if (TypeManager.customContains(genericTypeName)){
            for (int i = 0; i < length; i++) {
                dataList.add(generateCustom(genericTypeName, valueObject.getLevelCount() + 1));
            }
        } else {
            throw NotMatchedException.error(genericTypeSimpleName);
        }

        return dataList;
    }

    public static Map mapRandom() {
        int length = randomNumberGenerator.nextInt(5) + 2;
        String str = "0123456789";
        Map dataMap = new HashMap();
        for (int i = 0; i < length; i++) {
            dataMap.put(str.substring(randomNumberGenerator.nextInt(5), 6), str.substring(randomNumberGenerator.nextInt(5), 6));
        }
        return dataMap;
    }

    public static Map mapRandom(ValueObject valueObject) {
        if (valueObject.getLevelCount() > ObjectGenerator.getNestedLevelCount()) {
            return null;
        }
        int length = randomNumberGenerator.nextInt(5) + 2;
        Map dataMap = new HashMap();
        String keyTypeName = valueObject.getGenericTypeSimpleNameList().get(0);
        ValueObject valueGeneric = new ValueObject();
        valueGeneric.setSimpleTypeName(valueObject.getGenericTypeSimpleNameList().get(1));
        valueGeneric.setTypeName(valueObject.getGenericTypeNameList().get(1));
        valueGeneric.setLevelCount(valueObject.getLevelCount());
        for (int i = 0; i < length; i++) {
            dataMap.put(basicDataGeneratorMap.get(keyTypeName).get(), ValueGenerator.getConcreteTypeValue(valueGeneric));
        }

        return dataMap;
    }

    public static Object arrayRandom(ValueObject valueObject) {
        if (valueObject.getLevelCount() > ObjectGenerator.getNestedLevelCount()) {
            return null;
        }
        String genericTypeSimpleName = valueObject.getGenericTypeSimpleNameList().get(0);
        String genericTypeName = valueObject.getGenericTypeNameList().get(0);
        int length = randomNumberGenerator.nextInt(5) + 2;
        Object objects = null;
        if (TypeManager.basicContains(genericTypeSimpleName)) {
            objects = createArray(TypeManager.getBasicType(genericTypeSimpleName), length);
            for (int i = 0; i < length; i++) {
                Array.set(objects, i, basicDataGeneratorMap.get(genericTypeSimpleName).get()); ;
            }
        } else if (TypeManager.customContains(genericTypeName)) {
            objects = createArray(TypeManager.getCustomType(genericTypeName), length);
            for (int i = 0; i < length; i++) {
                Array.set(objects, i, generateCustom(genericTypeName, valueObject.getLevelCount() + 1));
            }
        }

        return objects;
    }

    /**
     * create array
     *
     * @param cls
     * @param length
     * @return
     */
    public static Object createArray(Class<?> cls, int length){
        return Array.newInstance(cls, length);
    }

    public static String getRandomData() {
        return randomData;
    }

    public static void setRandomData(String randomData) {
        ValueGenerator.randomData = randomData;
    }
}
